package com.example.felord.oauth2_1samples.config;

import com.example.felord.oauth2_1samples.wechat.WechatOAuth2AuthorizationCodeGrantRequestEntityConverter;
import com.example.felord.oauth2_1samples.wechat.WechatOAuth2AuthorizationRequestCustomizer;
import com.example.felord.oauth2_1samples.wechat.WechatOAuth2UserService;
import com.google.common.collect.Lists;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.DefaultMapOAuth2AccessTokenResponseConverter;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;
import java.util.Map;

@Configuration(proxyBeanMethods = false)
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception {
        OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService =
                new DelegatingOAuth2UserService<>(Collections.singletonMap("wechat",new WechatOAuth2UserService()));
        //注册OAuth2请求解析器
        OAuth2AuthorizationRequestResolver requestResolver = customOAuth2AuthorizationRequestResolver(clientRegistrationRepository);

        OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = accessTokenResponseClient();

        http.authorizeRequests(request -> request.anyRequest().authenticated())
                .oauth2Login()
                .authorizationEndpoint()
                .authorizationRequestResolver(requestResolver)
                .and()
                .tokenEndpoint()
                .accessTokenResponseClient(accessTokenResponseClient)
                .and()
                .userInfoEndpoint()
                .userService(oAuth2UserService);
        http.oauth2Client()
                .authorizationCodeGrant()
                .authorizationRequestResolver(requestResolver)
                .accessTokenResponseClient(accessTokenResponseClient);

        return http.build();
    }

    /**
     * customize the http client to get access_token from wechat token-uri
     * main logic : the wechat response body format sees like json but their media type 'Content-Type' is 'text-plain'
     * @return OAuth2AccessTokenResponseClient
     */
    private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
        DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
        tokenResponseClient.setRequestEntityConverter(new WechatOAuth2AuthorizationCodeGrantRequestEntityConverter());
        OAuth2AccessTokenResponseHttpMessageConverter responseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
        // 微信返回的content-type 是 text-plain
        responseHttpMessageConverter.setSupportedMediaTypes(
                Lists.newArrayList(MediaType.APPLICATION_JSON,MediaType.TEXT_PLAIN,new MediaType("application","*+json")));

        //兼容微信token接口返回转换的工具，扩展了{@link DefaultMapOAuth2AccessTokenResponseConverter}
        responseHttpMessageConverter.setAccessTokenResponseConverter((tokenResponseParameters) -> {
            final Converter<Map<String, Object>, OAuth2AccessTokenResponse> delegate = new DefaultMapOAuth2AccessTokenResponseConverter();
            tokenResponseParameters.put(OAuth2ParameterNames.TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER.getValue());
            return delegate.convert(tokenResponseParameters);
        });

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setMessageConverters(Lists.newArrayList(new FormHttpMessageConverter(),responseHttpMessageConverter));
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
        tokenResponseClient.setRestOperations(restTemplate);

        return tokenResponseClient;
    }

    /**
     * Being used to collect parameters from {@link javax.servlet.http.HttpServletRequest}
     *  and package to OAuth2 Request {@link org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest}
     * @param clientRegistrationRepository the client Registration Repository
     * @return instance of OAuth2AuthorizationRequestResolver
     */
    private OAuth2AuthorizationRequestResolver customOAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
        // if you changed the base uri, should config in here
        DefaultOAuth2AuthorizationRequestResolver requestResolver =
                new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
        //custom wechat request, if more, setting here
        requestResolver.setAuthorizationRequestCustomizer((builder -> new WechatOAuth2AuthorizationRequestCustomizer("wechat").customize(builder)));
        return requestResolver;
    }

    @Bean
    UserDetailsService userDetailsService(){
        return (username) ->
            User.withUsername("susan")
                    .password("123456")
                    .authorities("ROLE_ADMIN","ROLE_USER").build();
    }
}
